﻿//#define PATH_VERBOSE
using System;
using System.Collections;
using System.Linq;
using System.Collections.Generic;
using RayDen.Library.Components.SystemComponents;
using RayDen.RayEngine.Core;
using RayDen.RayEngine.Core.Interface;
using RayDen.RayEngine.Core.Types;
using RayDen.RayEngine.Data;
using RayDen.RayEngine.Samplers;

namespace RayDen.RayEngine
{
    public abstract class PathBufferBase<TPath> : IEnumerable<TPath>, IPathProcessor
        where TPath:IPathSampler
    {
        public RayEngineScene Scene;
        public long statsRenderingStart;
        public long statsTotalSampleCount;
        protected IImageFilm film;

        protected SampleBuffer sampleBuffer;
        public ISampler Sampler;

        protected int firstPath, lastPath, maxPaths;
        protected List<TPath> paths;


        public virtual void FillRayBuffer(RayBuffer rayBuffer)
        {
            if (paths.Count == 0)
            {
                // Need at least 2 paths
                paths.Add(this.CreatePathSampler());
                paths.Add(this.CreatePathSampler());
                paths.Add(this.CreatePathSampler());

                firstPath = 0;
            }

            bool allPathDone = true;
            lastPath = firstPath;
            for (; ; )
            {
                if (!paths[lastPath].FillRayBuffer(rayBuffer))
                {
                    // Not enough free space in the RayBuffer
                    allPathDone = false;
                    break;
                }

                lastPath = (lastPath + 1) % paths.Count;
                if (lastPath == firstPath)
                    break;
            }

            if (allPathDone)
            {
                var newPaths = 0;
                var maxNewPaths = maxPaths;

                for (; ; )
                {
                    newPaths++;
                    if (newPaths > maxNewPaths)
                    {
                        firstPath = 0;
                        lastPath = paths.Count - 1;
                        break;
                    }

                    var p = this.CreatePathSampler();
                    paths.Add(p);
                    p.InitPath(this);
                    if (!p.FillRayBuffer(rayBuffer))
                    {
                        firstPath = 0;
                        lastPath = paths.Count - 2;
                        break;
                    }
                }
            }
        }

        public virtual void AdvancePaths(RayBuffer rayBuffer)
        {
#if VERBOSE
            try
            {
#endif
            for (int i = firstPath; i != lastPath; i = (i + 1) % paths.Count)
            {
                paths[i].Advance(rayBuffer, this.sampleBuffer);

                if (this.sampleBuffer.IsFull())
                {
                    this.statsTotalSampleCount += this.sampleBuffer.GetSampleCount();
                    // Splat all samples on the film
                    this.film.SplatSampleBuffer(true, sampleBuffer);
                    this.sampleBuffer = this.film.GetFreeSampleBuffer();
                }
            }

            firstPath = (lastPath + 1) % paths.Count;

#if VERBOSE
            }
            catch (Exception ex)
            {
                Tracer.TraceLine(ex.Message);
                Tracer.TraceLine(ex.StackTrace);
                throw;
            }
#if PATH_VERBOSE
            Console.WriteLine("{0} Created Paths / {1} Splatted Paths / {2} Instances currently", createdCount, splattedCount, this.paths.Count);
#endif
#endif
            /*
            foreach (var pathSampler in paths) {
                if (pathSampler.RayIndex > 0)
                    pathSampler.Advance(rayBuffer, film, item => item.InitPath(this));
            }*/
        }
        protected abstract TPath CreatePathSampler();

        public virtual void ClearPaths()
        {
            paths.Clear();            
        }
        public abstract void ReInit();

        public virtual void Init(int maxPathsCount, RayEngineScene scene, IImageFilm agg, SamplingContext sc)
        {
            this.maxPaths = maxPathsCount;
            this.Scene = scene;
            this.film = agg;
            this.sampleBuffer = film.GetFreeSampleBuffer();
            this.paths = new List<TPath>(scene.MaxPaths + 1);
            this.Sampler = sc.PrimarySpaceSampler;
            
        }


        public IEnumerator<TPath> GetEnumerator()
        {
            return this.paths.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    public abstract class PathBufferBase : PathBufferBase<PathSamplerBase>
    {

        public LightSampler lightSampler;
        public SurfaceSampler surfaceSampler;

        protected internal int ThreadId;



        internal bool volumePaths = false; // TODO 
        protected internal long createdCount;
        protected internal long splattedCount;

        public PathBufferBase()
        {
            volumePaths = false;
        }

        public override void Init(int maxPaths, RayEngineScene scene, Core.Interface.IImageFilm agg, SamplingContext sc)
        {
            base.Init(maxPaths,scene, agg, sc);
            this.surfaceSampler = (SurfaceSampler)sc.SurfaceSampler;
            this.lightSampler = (LightSampler)sc.LightSampler;
           
        }

        public override void ReInit()
        {
            if (paths == null)
            {
                paths = new List<PathSamplerBase>(maxPaths);                
            }

            for (var i = 0; i < paths.Count; ++i)
                paths[i].InitPath(this);
            firstPath = 0;
            //film.Reset();

            statsRenderingStart = DateTime.UtcNow.Ticks / 1000;
            statsTotalSampleCount = 0;
        }


        public override void FillRayBuffer(RayBuffer rayBuffer)
        {
            if (paths.Count == 0)
            {
                // Need at least 2 paths
                paths.Add(this.CreatePathSampler());
                paths.Add(this.CreatePathSampler());
                paths.Add(this.CreatePathSampler());

                paths[0].InitPath(this);
                paths[1].InitPath(this);
                paths[2].InitPath(this);

                firstPath = 0;
            }

            bool allPathDone = true;
            lastPath = firstPath;
            for (; ; )
            {
                if (!paths[lastPath].FillRayBuffer(rayBuffer))
                {
                    // Not enough free space in the RayBuffer
                    allPathDone = false;
                    break;
                }

                lastPath = (lastPath + 1) % paths.Count;
                if (lastPath == firstPath)
                    break;
            }

            if (allPathDone)
            {
                var newPaths = 0;
                var maxNewPaths = maxPaths;

                for (; ; )
                {
                    newPaths++;
                    if (newPaths > maxNewPaths)
                    {
                        firstPath = 0;
                        lastPath = paths.Count - 1;
                        break;
                    }

                    var p = this.CreatePathSampler();
                    paths.Add(p);
                    p.InitPath(this);
                    if (!p.FillRayBuffer(rayBuffer))
                    {
                        firstPath = 0;
                        lastPath = paths.Count - 2;
                        break;
                    }
                }
            }
        }

        public override void AdvancePaths(RayBuffer rayBuffer)
        {
#if VERBOSE
            try
            {
#endif
            for (int i = firstPath; i != lastPath; i = (i + 1) % paths.Count)
            {
                paths[i].Advance(rayBuffer, this.sampleBuffer);

                if (this.sampleBuffer.IsFull())
                {
                    this.statsTotalSampleCount += this.sampleBuffer.GetSampleCount();
                    // Splat all samples on the film
                    this.film.SplatSampleBuffer(true, sampleBuffer);
                    this.sampleBuffer = this.film.GetFreeSampleBuffer();
                }
            }

            firstPath = (lastPath + 1) % paths.Count;

#if VERBOSE
            }
            catch (Exception ex)
            {
                Tracer.TraceLine(ex.Message);
                Tracer.TraceLine(ex.StackTrace);
                throw;
            }
#if PATH_VERBOSE
            Console.WriteLine("{0} Created Paths / {1} Splatted Paths / {2} Instances currently", createdCount, splattedCount, this.paths.Count);
#endif
#endif
            /*
            foreach (var pathSampler in paths) {
                if (pathSampler.RayIndex > 0)
                    pathSampler.Advance(rayBuffer, film, item => item.InitPath(this));
            }*/
        }

        protected virtual void OnSplat(PathSamplerBase item)
        {
            item.InitPath(this);
        }

       
    }


    public class PathBuffer<TPath> : PathBufferBase, IEnumerable<TPath>
        where TPath : PathSamplerBase, new()
    {
        public PathBuffer() { }
        public PathBuffer(int maxPaths, RayEngineScene scene, ImageFilmBase agg, SamplingContext sc)
            : base()
        {
            this.Init(maxPaths, scene, agg, sc);
        }
        public virtual void SetupAggregator(IPathSamplerAggregator aggregator) { }


        protected override PathSamplerBase CreatePathSampler()
        {
#if VERBOSE
            createdCount++;
#endif
            return new TPath { SurfaceSampler = surfaceSampler, pathIntegrator = this, LightSampler = this.lightSampler };
        }

        #region IEnumerable<PathSamplerBase> Members

        public new IEnumerator<TPath> GetEnumerator()
        {
            return paths.Cast<TPath>().GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.GetEnumerator();
        }

        #endregion

        protected int NonZeroRadiance
        {
            get { return this.paths.Count(item => !item.Radiance.IsBlack()); }
        }

    }


    public class PathBuffer<TPath, TEngineContext> : PathBuffer<TPath>
        where TPath : PathSamplerBase, IContextSampler<TEngineContext>, new()
    {
        protected TEngineContext context;
        internal TEngineContext Context
        {
            get { return context; }
            set { context = value; }
        }
        public PathBuffer() { }
        public PathBuffer(int maxPaths, RayEngineScene scene, ImageFilmBase agg, SamplingContext sc, TEngineContext ec)
            : base(maxPaths, scene, agg, sc)
        {
            this.context = ec;
            this.Init(maxPaths, scene, agg, sc);
        }


        protected override PathSamplerBase CreatePathSampler()
        {
            var sampler = base.CreatePathSampler();
            ((IContextSampler<TEngineContext>)sampler).SetContext(context);
            return sampler;
        }


    }

}